if(!QSAPI){
	var QSAPI={};
}

QSAPI.Subscriber=function(pushPull,permissionChecker,cfg){
	this.cfg={
		dataType:'delay',
		type:'push',
		pushSubSeparator:'|'
	};
	if(cfg){
		$.extend(this.cfg,cfg);
	}
	this.dataObj=pushPull;
	this.permissionChecker=permissionChecker;
	this.subUSExchange = [41,11,9,30,29,33,22,25]; 
	this.subTickers={};
	this.subWidgets={};
};

QSAPI.Subscriber.prototype={
	setPermissionChecker:function(permissionChecker){
		this.permissionChecker=permissionChecker;
	},
	subscribe:function(component,tickerObjects,callbacks,needSubscribeLevelII){
		var self=this;
		tickerObjects=tickerObjects||[];
		var gkey=null,
			tickerObject=null,
			subscribePara=null,
			noPermissionTickerObjects=[],
			noTenforeInfoTickerObjects=[],
			successTickerObjects=[],
			channels=[];
		for(var i=0,l=tickerObjects.length;i<l;i++){
			tickerObject=tickerObjects[i];
			gkey=tickerObject.getField('gkey')
			if(gkey==null){
				noTenforeInfoTickerObjects.push(tickerObject);
			}else{
				subscribePara=self._getSubscribePara(tickerObject,needSubscribeLevelII);
				if(subscribePara.length>0){
					channels=channels.concat(self._buildSubscribeChannels(tickerObject,subscribePara,false,needSubscribeLevelII));
					if (tickerObject.getField('mType')=='FE') { // get IIV subscribe parameters.
						var subscribeParaIIV=self._getIIVSubscribeParas(tickerObject);
						if(subscribeParaIIV.length>0){
							channels=channels.concat(self._buildSubscribeChannels(tickerObject,subscribeParaIIV,true,needSubscribeLevelII));
						}
					}
					self._cacheSubscription(gkey,tickerObject,component);
					successTickerObjects.push(tickerObject);
				}else{
					noPermissionTickerObjects.push(tickerObject);
				}
			}
		}
		if(self.dataObj){
			if(typeof self.dataObj.batchSubscribe=='function' && channels.length>0){
				self.dataObj.batchSubscribe(channels);
			}
			var dataHandler=self.dataObj.getDataHandler();
			if(dataHandler && typeof dataHandler.forceUpdateQuoteData=='function'){
				dataHandler.forceUpdateQuoteData(component,successTickerObjects);
			}
		}
		if(typeof callbacks.onSuccess=='function' && successTickerObjects.length>0){
			callbacks.onSuccess({
				errorCode:'0',
				errorMessage:'Subscribe successfully.',
				tickerObjects:successTickerObjects
			})
		}
		if(typeof callbacks.onFailure=='function'&&(noTenforeInfoTickerObjects.length>0||noPermissionTickerObjects.length>0)){
			callbacks.onFailure({
				errorCode:'-4',
				errorMessage:'Some tickers objects can not be subscribed.',
				InvalidTickerObjects:noTenforeInfoTickerObjects,
				noPermissionTickerObjects:noPermissionTickerObjects
			})
		}
	
	},
	unSubscribe:function(component,tickerObjects,callbacks,needUnSubscribeLevelII){
		var self=this;
		self._unSubscribe(component,tickerObjects,callbacks,needUnSubscribeLevelII);
	},
	removeCache:function(component){
		var subscribeID=component.subscribeID,
			tickerObjects=[];
		if(this.subTickers[subscribeID]){
			for(var s in this.subTickers[subscribeID]){
				tickerObjects.push(this.subTickers[subscribeID][s]);
			}
		}
		this._unSubscribe(component,tickerObjects);
	},
	_unSubscribe:function(component,tickerObjects,callbacks,needUnSubscribeLevelII){
		callbacks=callbacks||{};
		var self=this;
		tickerObjects=tickerObjects||[];
		var gkey=null,
			tickerObject=null,
			subscribePara=null,
			channels=[],
			successTickerObjects=[],
			noSubscribedTickerObjects=[];
			
		for(var i=0,l=tickerObjects.length;i<l;i++){
			tickerObject=tickerObjects[i];
			gkey=tickerObject.getField('gkey');
			if(gkey==null){
				noSubscribedTickerObjects.push(tickerObject);
				continue;
			}else{
				var isValid=this._checkSubscribed(component.subscribeID,tickerObject);
				if(isValid){
					subscribePara=self._getSubscribePara(tickerObject,needUnSubscribeLevelII);
					if(subscribePara.length>0){
						channels=channels.concat(self._buildUnSubscribeChannels(tickerObject,subscribePara,false,needUnSubscribeLevelII));
						if (tickerObject.getField('mType')=='FE') { // get IIV subscribe parameters.
							var subscribeParaIIV=self._getIIVSubscribeParas(tickerObject);
							if(subscribeParaIIV.length>0){
								channels=channels.concat(self._buildUnSubscribeChannels(tickerObject,subscribeParaIIV,true,needUnSubscribeLevelII));
							}
						}
						self._removeSubscription(gkey,tickerObject,component);
						successTickerObjects.push(tickerObject);
					}else{
						noSubscribedTickerObjects.push(tickerObject);
					}
				}else{
					noSubscribedTickerObjects.push(tickerObject);
				}
			}
		}
		if(self.dataObj && typeof self.dataObj.batchUnsubscribe=='function' && channels.length>0){
			self.dataObj.batchUnsubscribe(channels);	
		}
		if(typeof callbacks.onSuccess=='function' && successTickerObjects.length>0){
			callbacks.onSuccess({
				errorCode:'0',
				errorMessage:'Unsubscribe successfully.',
				tickerObjects:successTickerObjects
			})
		}
		if(typeof callbacks.onFailure=='function'&&(noSubscribedTickerObjects.length>0)){
			callbacks.onFailure({
				errorCode:'-4',
				errorMessage:'Some tickers objects can not be unsubscribed.',
				InvalidTickerObjects:noSubscribedTickerObjects
			})
		}
	},
	_cacheSubscription:function(gkey,tickerObject,component){	
		var symbol=tickerObject.getField('symbol');
		var subscribeID=component.subscribeID;
		if(!this.subTickers[subscribeID]){
			this.subTickers[subscribeID]={};
		}
		this.subTickers[subscribeID][symbol]=tickerObject;
		
		if(!this.subWidgets[gkey]){
			this.subWidgets[gkey]={};
		}
		this.subWidgets[gkey][subscribeID]=component;
		
		var egkey=tickerObject.getField('eGkey');
		if(egkey){
			if(!this.subWidgets[egkey]){
				this.subWidgets[egkey]={};
			}
			this.subWidgets[egkey][subscribeID]=component;
		}
		
	},
	_removeSubscription:function(gkey,tickerObject,component){
		var symbol=tickerObject.getField('symbol');
		var subscribeID=component.subscribeID;
		var egkey=tickerObject.getField('eGkey');
		if(this.subWidgets[gkey]){
			delete this.subWidgets[gkey][subscribeID];
			if($.isEmptyObject(this.subWidgets[gkey])){
				delete this.subWidgets[gkey];
			}
		}
		if(egkey){
			if(this.subWidgets[egkey]){
				delete this.subWidgets[egkey][subscribeID];
				if($.isEmptyObject(this.subWidgets[egkey])){
					delete this.subWidgets[egkey];
				}
			}
		}
		if(this.subTickers[subscribeID]){
			delete this.subTickers[subscribeID][symbol];
			if($.isEmptyObject(this.subTickers[subscribeID])){
				delete this.subTickers[subscribeID];
			}
			
		}		
	},
	_buildSubscribeChannels:function(tickerObject,subscribePara,isIIV,needSubscribeLevelII){
		var channels=[],gkey=isIIV?tickerObject.getField('eGkey'):tickerObject.getField('gkey');
		if(!gData[gkey]){
			gData[gkey]={};
		}
		if (!gData[gkey]["subcribeNum"]) {//set subcribeNum to 0
			gData[gkey]["subcribeNum"] = 0;
		}
		if (!gData[gkey]["subcribeLevelIINum"]) {//set subcribeLevelIINum to 0
			gData[gkey]["subcribeLevelIINum"] = 0;
		}
		if (gData[gkey]["subcribeNum"] == 0 || (needSubscribeLevelII && gData[gkey]["subcribeLevelIINum"] == 0)) { // no subscribe this ticker.
			if(this.cfg.type == 'push') {
				channels = channels.concat(subscribePara);
			}else{
				var itemNames=subscribePara[0].itemName.split(this.cfg.pushSubSeparator);
				var exch=subscribePara[0].serviceName;//exchCode get from subscribe list not gkey
				var pullTicker=exch+"."+itemNames[0]+"."+itemNames[1];
				gData[gkey]["pullTicker"]=pullTicker;
				channels.push(pullTicker);
			}	
		}
		gData[gkey]["subcribeNum"]++;
		if (needSubscribeLevelII) {
			gData[gkey]["subcribeLevelIINum"]++;
		}
		return channels;
	},
	_buildUnSubscribeChannels:function(tickerObject,subscribePara,isIIV,needSubscribeLevelII){
		var channels=[],gkey=isIIV?tickerObject.getField('eGkey'):tickerObject.getField('gkey');
		if(!gData[gkey]){
			gData[gkey]={};
		}
		if (gData[gkey]["subcribeNum"]) {//set subcribeNum to 0
			gData[gkey]["subcribeNum"]--;
		}
		if (gData[gkey]["subcribeLevelIINum"]&& needSubscribeLevelII) {//set subcribeLevelIINum to 0
			gData[gkey]["subcribeLevelIINum"]--;
		}
		if(this.cfg.type == 'push') {
			if (gData[gkey]["subcribeNum"] == 0 || (needSubscribeLevelII && !gData[gkey]["subcribeLevelIINum"])){ // no subscribe this ticker.
				channels = channels.concat(subscribePara);
			}
		}
		return channels;
	},
	_getSubscribePara:function(tickerObject,needSubscribeLevelII){
		var eCode=tickerObject.getField('tenforeCode'),//getTenforeCode(),
			type=parseInt(tickerObject.getField('type'),10),
			tTicker=tickerObject.getField('tenforeTicker');
		var subcodes = this.permissionChecker.getSubcodes(type,eCode,tTicker,"R");
		var exchCode,aParams=[];
		if(subcodes.length>0){// Clients will subscribe real-time if they have real-time data permission.
			if (((type==1||type==10||type==8||type==2)&& eCode<=126&&eCode != 50)) { // USA stock,MF,Options and index(use 126 as exchange to subscribe level I data.)
				if(eCode != 41&&eCode!=11&&eCode!=9&&eCode!=30&&eCode!=29&&eCode!=33){
					exchCode = "126"; // 11,41 exchange is removed from 126 exchange.
				}else{
					exchCode=eCode;
				}
				if(type!=10||type!=2){//option and index no need to get USCOMP exchange subcodes               
                    subcodes = this.permissionChecker.getSubcodes(type,exchCode,tTicker,"R",false);  // get USCOMP exchange
                }
			}else{
				exchCode=eCode;
			}
			if (needSubscribeLevelII&& type == 1) { // level II for Stock,etf,Open-end Fund
                if (eCode <= 126&&eCode != 50) { // US exchange.
                    this._getSubcribeParas(aParams,type,"R",exchCode,subcodes,tTicker); // get levelI subscribe parameters.
                    this._getSubcribeParas(aParams, type, "R", 14, this.permissionChecker.getSubcodes(type, 14, tTicker, "R"), tTicker, true);
                    this._getSubcribeParas(aParams, type, "R", 19, this.permissionChecker.getSubcodes(type, 19, tTicker, "R"), tTicker, true);
                    this._getSubcribeParas(aParams, type, "R", 1, this.permissionChecker.getSubcodes(type, 1, tTicker, "R"), tTicker, true);
                    this._getSubcribeParas(aParams, type, "R", 34, this.permissionChecker.getSubcodes(type, 34, tTicker, "R"), tTicker, true);
                }else { // other exchange. get levelI and levelII data.
                    this._getSubcribeParas(aParams, type, "R", eCode, this.permissionChecker.getSubcodes(type, eCode, tTicker, "R"), tTicker, true);
                }
            }else{
				this._getSubcribeParas(aParams,type,"R",exchCode,subcodes,tTicker); // get levelI subscribe parameters.
			}
		}else{//check the delayed and bats data permission.
            if (eCode > 126||(this.cfg.dataType=="delay"||this.cfg.dataType=="mix")) {   // Non_USA ticker or dataType is delay or mix for USA Tickerr. 
                exchCode = "";
                if ((type==1||type==10||type==8||type==2)&& eCode<=126&& $.inArray(parseInt(eCode,10),this.permissionChecker.subUSExchange)<0) { // USA stock,MF, Options and index(use 126 as exchange to subscribe level I data.)
                    exchCode = "126"; 
                }else {
                    exchCode = eCode;
                }
				if(type==2){
					subcodes = this.permissionChecker.getSubcodes(type,eCode,tTicker,"D",false);  // get individual exchange subcodes when it is option
				}else{
					subcodes = this.permissionChecker.getSubcodes(type,exchCode,tTicker,"D",false);  // get USCOMP exchange
                }
				this._getSubcribeParas(aParams,type,"D",exchCode,subcodes,tTicker); // get levelI subscribe parameters.
            }
            if (eCode <= 126&&eCode != 50 && (this.cfg.dataType == "bats" || this.cfg.dataType == "mix") && (type==1)) { //user has order bats data for stock and etf in US market;
                this._getSubcribeParas(aParams,type,"R",17,this.permissionChecker.getSubcodes(type,17,tTicker,"R",false),tTicker)
            }
		}
		return aParams;
	},
	_getIIVSubscribeParas: function(tickerObject){
		var aParams=[],gkey=tickerObject.getField('eGkey');
		if(gkey && gData[gkey]){
			var etf=gData[gkey], dy = "D", etfSubcodes = this.permissionChecker.getSubcodes(10, etf["tenforeCode"], etf["tenforeTicker"], "R", false);
			if (etfSubcodes.length>0) {
				this._getSubcribeParas(aParams, 10, "R", etf["tenforeCode"], etfSubcodes, etf["tenforeTicker"]);
			} else {
				etfSubcodes = this.permissionChecker.getSubcodes(10, etf["tenforeCode"], etf["tenforeTicker"], "D", false);
				if (etfSubcodes.length) {
					this._getSubcribeParas(aParams, 10, "D", etf["tenforeCode"], etfSubcodes, etf["tenforeTicker"]);
				}
			}
		}
		return aParams;
    },
	_getSubcribeParas:function(aParams,sType,dataType,exchCode,subcodes,tTicker,isLevelII){
		 var rItem = sType + this.cfg.pushSubSeparator + tTicker+this.cfg.pushSubSeparator,item;
         for (var i = 0; i < subcodes.length; i++) {
            if(typeof(subcodes[i])=="undefined"||subcodes[i]=="") continue;  //
            if(isLevelII&&subcodes[i] == "-1")continue;
            item = rItem + subcodes[i];
            if(isLevelII)item = item+this.cfg.pushSubSeparator+"NN"; // no netting for level II data.
            aParams.push({
                sourceType: dataType,
                serviceName: exchCode,
                itemName: item,
                isLevelII:isLevelII
            });
        }
    },
	_getGKey:function(tickerObject){
		var tenforeExch=tickerObject.getField('tenforeCode'),
			tenforeSecType=tickerObject.getField('type'),
			tenforeTicker=tickerObject.getField('tenforeTicker');
		if(tenforeExch&&tenforeSecType&&tenforeTicker){
			var mType=tickerObject.getField('mType');
			if(tenforeExch<=126&&(tenforeSecType==1||tenforeSecType==10||tenforeSecType==8||mType=="XI"||mType=="ST"||mType=="FE"||mType=="FC"||mType=="FO"||mType=="--"||mType=="")){
				tenforeExch = 126;
			}
			return tenforeExch+":"+tenforeTicker;
		}else{
			return null;
		}	
	},
	_checkSubscribed:function(subscribeID,tickerObject,isCheckLevelII){
		var symbol=tickerObject.getField('symbol');
		if(this.subTickers[subscribeID]){
			if(this.subTickers[subscribeID][symbol]){
				return true;			
			}
		}
		return false;
	}
};